package trade

import (
	"context"
	"math/big"
	"strings"

	nftCommon "gitcode.net/togolife/nfttoken/common"
	"gitcode.net/togolife/nfttoken/contract"
	"github.com/ethereum/go-ethereum/accounts"
	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/common"
)

func getAccountAccordAddress(caller string) *accounts.Account {
	if txConf.LocalWallet == nil {
		return nil
	}
	currAccounts := txConf.LocalWallet.Accounts()
	var account *accounts.Account = nil
	for _, tmp := range currAccounts {
		if strings.ToLower(tmp.Address.Hex()) == strings.ToLower(caller) {
			account = &tmp
			break
		}
	}
	return account
}

func getNftContractTransaction(reqChain, contractAddr, caller string) (*contract.Contract, *bind.TransactOpts, nftCommon.NftError) {
	account := getAccountAccordAddress(caller)
	if account == nil {
		txLog.LogE("We can not visit caller address.")
		return nil, nil, nftCommon.NewError(501)
	}
	keyJson, err := txConf.LocalWallet.Export(*account, txConf.SecretWord, txConf.SecretWord)
	if err != nil {
		txLog.LogE("Export caller keystore failed! [%v]", err)
		return nil, nil, nftCommon.NewError(501)
	}
	client, nftErr := GetEthClient(reqChain)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get eth client failed! [%v]", nftErr.ErrorMsg)
		return nil, nil, nftErr
	}
	nftContract, err := contract.NewContract(common.HexToAddress(contractAddr), client)
	if err != nil {
		txLog.LogE("Get nft contract failed! [%v]", err)
		return nil, nil, nftCommon.NewError(506)
	}
	v, _ := txConf.SupportChains[reqChain]
	opts, err := bind.NewTransactorWithChainID(strings.NewReader(string(keyJson)), txConf.SecretWord, big.NewInt(int64(v.ChainId)))
	if err != nil {
		txLog.LogE("Create transaction option failed! [%v]", err)
		return nil, nil, nftCommon.NewError(500)
	}
	return nftContract, opts, nftCommon.NewError(200)
}

func Mint(reqChain, contractAddr, caller, to, tokenid, uri string) (string, nftCommon.NftError) {
	tid, ok := big.NewInt(0).SetString(tokenid, 10)
	if !ok {
		txLog.LogE("Input token id [%v] not correct!", tokenid)
		return "", nftCommon.NewError(403)
	}
	nftContract, opts, nftErr := getNftContractTransaction(reqChain, contractAddr, caller)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract and transaction options failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	tx, err := nftContract.Mint(opts, common.HexToAddress(to), tid, uri)
	if err != nil {
		txLog.LogE("Call contract Mint failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return tx.Hash().String(), nftCommon.NewError(200)
}

func Burn(reqChain, contractAddr, caller, tokenid string) (string, nftCommon.NftError) {
	tid, ok := big.NewInt(0).SetString(tokenid, 10)
	if !ok {
		txLog.LogE("Input token id [%v] not correct!", tokenid)
		return "", nftCommon.NewError(403)
	}
	nftContract, opts, nftErr := getNftContractTransaction(reqChain, contractAddr, caller)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract and transaction options failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	tx, err := nftContract.Burn(opts, tid)
	if err != nil {
		txLog.LogE("Call contract Burn failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return tx.Hash().String(), nftCommon.NewError(200)
}

func SafeTransferFrom(reqChain, contractAddr, caller, from, to, tokenid string) (string, nftCommon.NftError) {
	tid, ok := big.NewInt(0).SetString(tokenid, 10)
	if !ok {
		txLog.LogE("Input token id [%v] not correct!", tokenid)
		return "", nftCommon.NewError(403)
	}
	nftContract, opts, nftErr := getNftContractTransaction(reqChain, contractAddr, caller)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract and transaction options failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	tx, err := nftContract.SafeTransferFrom(opts, common.HexToAddress(from), common.HexToAddress(to), tid)
	if err != nil {
		txLog.LogE("Call contract SafeTransferFrom failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return tx.Hash().String(), nftCommon.NewError(200)
}

func SafeTransferFromWithData(reqChain, contractAddr, caller, from, to, tokenid string, data []byte) (string, nftCommon.NftError) {
	tid, ok := big.NewInt(0).SetString(tokenid, 10)
	if !ok {
		txLog.LogE("Input token id [%v] not correct!", tokenid)
		return "", nftCommon.NewError(403)
	}
	nftContract, opts, nftErr := getNftContractTransaction(reqChain, contractAddr, caller)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract and transaction options failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	tx, err := nftContract.SafeTransferFrom0(opts, common.HexToAddress(from), common.HexToAddress(to), tid, data)
	if err != nil {
		txLog.LogE("Call contract SafeTransferFrom0 failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return tx.Hash().String(), nftCommon.NewError(200)
}

func TransferFrom(reqChain, contractAddr, caller, from, to, tokenid string) (string, nftCommon.NftError) {
	tid, ok := big.NewInt(0).SetString(tokenid, 10)
	if !ok {
		txLog.LogE("Input token id [%v] not correct!", tokenid)
		return "", nftCommon.NewError(403)
	}
	nftContract, opts, nftErr := getNftContractTransaction(reqChain, contractAddr, caller)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract and transaction options failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	tx, err := nftContract.TransferFrom(opts, common.HexToAddress(from), common.HexToAddress(to), tid)
	if err != nil {
		txLog.LogE("Call contract TransferFrom failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return tx.Hash().String(), nftCommon.NewError(200)
}

func Approve(reqChain, contractAddr, caller, approved, tokenid string) (string, nftCommon.NftError) {
	tid, ok := big.NewInt(0).SetString(tokenid, 10)
	if !ok {
		txLog.LogE("Input token id [%v] not correct!", tokenid)
		return "", nftCommon.NewError(403)
	}
	nftContract, opts, nftErr := getNftContractTransaction(reqChain, contractAddr, caller)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract and transaction options failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	tx, err := nftContract.Approve(opts, common.HexToAddress(approved), tid)
	if err != nil {
		txLog.LogE("Call contract Approve failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return tx.Hash().String(), nftCommon.NewError(200)
}

func SetApprovalForAll(reqChain, contractAddr, caller, approved string, flag bool) (string, nftCommon.NftError) {
	nftContract, opts, nftErr := getNftContractTransaction(reqChain, contractAddr, caller)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract and transaction options failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	tx, err := nftContract.SetApprovalForAll(opts, common.HexToAddress(approved), flag)
	if err != nil {
		txLog.LogE("Call contract SetApprovalForAll failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return tx.Hash().String(), nftCommon.NewError(200)
}

// 查询方法
func getNftContractForQuery(reqChain, contractAddr string) (*contract.Contract, nftCommon.NftError) {
	client, nftErr := GetEthClient(reqChain)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get eth client failed! [%v]", nftErr.ErrorMsg)
		return nil, nftErr
	}
	nftContract, err := contract.NewContract(common.HexToAddress(contractAddr), client)
	if err != nil {
		txLog.LogE("Get nft contract failed! [%v]", err)
		return nil, nftCommon.NewError(506)
	}
	return nftContract, nftCommon.NewError(200)
}

func Owner(reqChain, contractAddr string) (string, nftCommon.NftError) {
	nftContract, nftErr := getNftContractForQuery(reqChain, contractAddr)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	address, err := nftContract.Owner(&bind.CallOpts{})
	if err != nil {
		txLog.LogE("Call Owner func failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return address.String(), nftCommon.NewError(200)
}

func Name(reqChain, contractAddr string) (string, nftCommon.NftError) {
	nftContract, nftErr := getNftContractForQuery(reqChain, contractAddr)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	name, err := nftContract.Name(&bind.CallOpts{})
	if err != nil {
		txLog.LogE("Call name func failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return name, nftCommon.NewError(200)
}

func Symbol(reqChain, contractAddr string) (string, nftCommon.NftError) {
	nftContract, nftErr := getNftContractForQuery(reqChain, contractAddr)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	symbol, err := nftContract.Symbol(&bind.CallOpts{})
	if err != nil {
		txLog.LogE("Call symbol func failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return symbol, nftCommon.NewError(200)
}

func TokenUri(reqChain, contractAddr, tokenid string) (string, nftCommon.NftError) {
	tid, ok := big.NewInt(0).SetString(tokenid, 10)
	if !ok {
		txLog.LogE("Input token id [%v] not correct!", tokenid)
		return "", nftCommon.NewError(403)
	}
	nftContract, nftErr := getNftContractForQuery(reqChain, contractAddr)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	uri, err := nftContract.TokenURI(&bind.CallOpts{}, tid)
	if err != nil {
		txLog.LogE("Call TokenUri func failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return uri, nftCommon.NewError(200)
}

func BalanceOf(reqChain, contractAddr, queryAddr string) (string, nftCommon.NftError) {
	nftContract, nftErr := getNftContractForQuery(reqChain, contractAddr)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract failed! [%v]", nftErr.ErrorMsg)
		return "0", nftErr
	}
	balance, err := nftContract.BalanceOf(&bind.CallOpts{}, common.HexToAddress(queryAddr))
	if err != nil {
		txLog.LogE("Call BalanceOf func failed! [%v]", err)
		return "0", nftCommon.NewError(502)
	}
	return balance.String(), nftCommon.NewError(200)
}

func OwnerOf(reqChain, contractAddr, tokenid string) (string, nftCommon.NftError) {
	tid, ok := big.NewInt(0).SetString(tokenid, 10)
	if !ok {
		txLog.LogE("Input token id [%v] not correct!", tokenid)
		return "", nftCommon.NewError(403)
	}
	nftContract, nftErr := getNftContractForQuery(reqChain, contractAddr)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	address, err := nftContract.OwnerOf(&bind.CallOpts{}, tid)
	if err != nil {
		txLog.LogE("Call OwnerOf func failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return address.String(), nftCommon.NewError(200)
}

func TotalSupply(reqChain, contractAddr string) (string, nftCommon.NftError) {
	nftContract, nftErr := getNftContractForQuery(reqChain, contractAddr)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract failed! [%v]", nftErr.ErrorMsg)
		return "0", nftErr
	}
	total, err := nftContract.TotalSupply(&bind.CallOpts{})
	if err != nil {
		txLog.LogE("Call TotalSupply func failed! [%v]", err)
		return "0", nftCommon.NewError(502)
	}
	return total.String(), nftCommon.NewError(200)
}

func GetApproved(reqChain, contractAddr, tokenid string) (string, nftCommon.NftError) {
	tid, ok := big.NewInt(0).SetString(tokenid, 10)
	if !ok {
		txLog.LogE("Input token id [%v] not correct!", tokenid)
		return "", nftCommon.NewError(403)
	}
	nftContract, nftErr := getNftContractForQuery(reqChain, contractAddr)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract failed! [%v]", nftErr.ErrorMsg)
		return "", nftErr
	}
	approved, err := nftContract.GetApproved(&bind.CallOpts{}, tid)
	if err != nil {
		txLog.LogE("Call GetApproved func failed! [%v]", err)
		return "", nftCommon.NewError(502)
	}
	return strings.ToLower(approved.Hex()), nftCommon.NewError(200)
}

func IsApprovedForAll(reqChain, contractAddr, owner, approved string) (bool, nftCommon.NftError) {
	nftContract, nftErr := getNftContractForQuery(reqChain, contractAddr)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get nft contract failed! [%v]", nftErr.ErrorMsg)
		return false, nftErr
	}
	flag, err := nftContract.IsApprovedForAll(&bind.CallOpts{}, common.HexToAddress(owner), common.HexToAddress(approved))
	if err != nil {
		txLog.LogE("Call IsApprovedForAll func failed! [%v]", err)
		return false, nftCommon.NewError(502)
	}
	return flag, nftCommon.NewError(200)
}

// 查询交易信息
func QueryTransactionResult(reqChain, tx string) (int, nftCommon.NftError) {
	client, nftErr := GetEthClient(reqChain)
	if !nftCommon.Valid(nftErr) {
		txLog.LogE("Get eth client failed! [%v]", nftErr.ErrorMsg)
		return 0, nftErr
	}
	receipt, err := client.TransactionReceipt(context.Background(), common.HexToHash(tx))
	if err != nil {
		txLog.LogE("TransactionReceipt failed! [%v]", err)
		return 0, nftCommon.NewError(502)
	}
	return int(receipt.Status), nftCommon.NewError(200)
}
